-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Add support for text mints #411
Conversation
- Create new textarea input component for typed based nfts that renders the correct fonts to be displayed correctly on OBJKT - Modify existing FormFields to include new select options for user to specify if it's a typed nft and whether it requires monospace font - Auto generate cover image based on typed input
- Description will be automatially be the typed input; hide description field - Rename Monospace checkbox field
Thanks, almost good for me, besides the comment I just made I have a few questions:
We can clean it up more in a separate PR, but fI think it's mostly fine for now if the metadata are good for everyone |
src/utils/mint.ts
Outdated
export const generateTypedArtCoverImage = async ( | ||
txt: string, | ||
monospace: boolean | ||
) => { | ||
const font = monospace ? 'IBM Plex Mono' : 'Source Sans Pro' | ||
const cv_font = `16px ${font}` | ||
if (txt.length === 0) { | ||
return false | ||
} | ||
const createContext = function (width: number, height: number) { | ||
const canvas = document.createElement('canvas') | ||
canvas.width = width | ||
canvas.height = height | ||
const ctx = canvas.getContext('2d') | ||
if (!ctx) throw new Error('Could not create canvas context') | ||
return ctx | ||
} | ||
const c = createContext(512, 512) | ||
c.filter = 'grayscale(100%)' | ||
|
||
let dize = [] | ||
const lines = txt.split('\n') | ||
dize = lines | ||
c.font = cv_font | ||
const x = 0 | ||
const y = 16 | ||
const lineheight = 16 | ||
const result = dize.reduce( | ||
(r, e) => (c.measureText(r).width < c.measureText(e).width ? e : r), | ||
'' | ||
) | ||
if (result.length > 130) { | ||
c.canvas.width = 512 | ||
c.canvas.height = 512 | ||
} else { | ||
c.canvas.width = c.measureText(result).width | ||
c.canvas.height = lineheight * dize.length + 12 | ||
} | ||
c.fillStyle = 'transparent' | ||
c.fillRect(0, 0, c.canvas.width, c.canvas.height) | ||
c.filter = 'grayscale(100%)' | ||
c.fillStyle = 'white' | ||
if (c.canvas.width === 512) { | ||
const s = lines[0].substring(0, 20) | ||
c.fillText(s + '...', 16, 256) | ||
} else { | ||
for (let i = 0; i < lines.length; i++) { | ||
c.fillText(lines[i], x, y + i * lineheight) | ||
} | ||
} | ||
const ca = createContext(512, 512) | ||
ca.fillStyle = 'transparent' | ||
ca.fillRect(0, 0, 512, 512) | ||
const cerceve = createContext(512, 512) | ||
const cImage = new Image() | ||
const img = new Image() | ||
return new Promise((resolve, reject) => { | ||
img.src = c.canvas.toDataURL('svg') | ||
img.onload = function () { | ||
ca.imageSmoothingEnabled = true | ||
ca.width = 512 | ||
ca.height = 512 | ||
const hRatio = ca.width / img.width | ||
const vRatio = ca.height / img.height | ||
const ratio = Math.min(hRatio, vRatio) | ||
const centerShift_x = (ca.width - img.width * ratio) / 2 | ||
const centerShift_y = (ca.height - img.height * ratio) / 2 | ||
ca.drawImage( | ||
img, | ||
0, | ||
0, | ||
img.width, | ||
img.height, | ||
centerShift_x, | ||
centerShift_y, | ||
img.width * ratio, | ||
img.height * ratio | ||
) | ||
const t = ca.canvas.toDataURL('svg') | ||
cImage.src = t | ||
cImage.onload = async function () { | ||
cerceve.fillStyle = 'transparent' | ||
cerceve.fillRect(0, 0, 512, 512) | ||
cerceve.drawImage(cImage, 0, 0, 512, 512, 16, 16, 512 - 16, 512 - 16) | ||
const ctx = cerceve.canvas.toDataURL('svg') | ||
const res = await fetch(ctx) | ||
const blob = await res.blob() | ||
const file = new File([blob], 'Generated Cover.png', { | ||
type: 'image/png', | ||
}) | ||
|
||
resolve(file) | ||
} | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you simplify/refactor this a bit for clarity? I moved the text.length check first to un-nest it, changed ts types and use only async/await but I'm still not fully getting the logic of it.
Regarding the metadata, it follows typedart logic (minus the on chain text) so it's actually fine! |
d3cf78e
to
74bde4d
Compare
When checking the generated previews I get weird results: https://cache.teia.rocks/ipfs/QmYdXkZM2nCX38gwrdsD21VG1Vq2P4eur1DgDMu3aLocWZ Another thing is that it's currently not handling single long lines and I'm not sure what we want to happen in these cases: input text
|
Not coming from that PR directly but revealed by it. Also adds ts,tsx and json to the linters.
Mainly simplifying code, reusing variables. This also adds a preview of the generation to the mint form directly.
Thanks Ziroh |
This PR adds support for ASCII text mints. There are two new options added to the
MintForm
component - This is a text mint checkbox and a monospace font required checkbox.For typed mints, the cover and thumbnail images are automatically generated based on the text input and the font selected. The description will also automatically be replaced by the text mint input.
Minting Form
The textarea renders the monospace (IBM Plex Mono) / non-monospace font (Source Sans Pro) depending on the monospace font required checkbox value.
Monospace:
Non-monospace:
Preview / Token Page
Example: /objkt/850488
The preview component displays the typed art, in either a monospace font a non-monospace font (Source Sans Pro). If Monospace is selected, a 'monospace' tag is automatically added to ensure that it renders properly on OBJKT; and viceversa.
Feed Page
Text NFTs are rendered as their cover/thumbnail images on the feed listing page, similar to how OBJKT does it. The actual text mint is only rendered as in the individual token page.
Notes
--
Update [21/04]:
Non-text mint
Text mint checked
Update [05/05]: